/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#define LOG_TAG "ISharedResultSetStub"
#include "ishared_result_set_stub.h"

#include <atomic>
#include <future>

#include "logger.h"
#include "rdb_errno.h"
#include "shared_block.h"

std::atomic<uint32_t> g_sharedBlockName = 1;  // 1: shared block name

namespace OHOS::NativeRdb {
using namespace OHOS::Rdb;
std::function<sptr<ISharedResultSet>(std::shared_ptr<ResultSet>, MessageParcel &)>
    ISharedResultSet::providerCreator_ = ISharedResultSetStub::CreateStub;
constexpr ISharedResultSetStub::Handler ISharedResultSetStub::handlers[static_cast<uint32_t>(ResultSetCode::FUNC_BUTT)];

sptr<ISharedResultSet> ISharedResultSetStub::CreateStub(
    std::shared_ptr<ResultSet> result, OHOS::MessageParcel &parcel)
{
    sptr<ISharedResultSet> stub = new (std::nothrow) ISharedResultSetStub(result);
    if (stub == nullptr) {
        LOG_ERROR("Stub is nullptr.");
        return nullptr;
    }
    if (result == nullptr) {
        LOG_ERROR("Result is nullptr.");
        return nullptr;
    }
    parcel.WriteRemoteObject(stub->AsObject());
    auto writer_ = std::make_shared<BlockWriter>(std::to_string(g_sharedBlockName++), DEFAULT_BLOCK_SIZE);
    auto block = writer_->GetBlock();
    if (block != nullptr) {
        block->WriteMessageParcel(parcel);
    }
    return stub;
}

ISharedResultSetStub::ISharedResultSetStub(std::shared_ptr<ResultSet> resultSet)
    : resultSet_(std::move(resultSet))
{
}
ISharedResultSetStub::~ISharedResultSetStub()
{
}

int ISharedResultSetStub::OnRemoteRequest(
    uint32_t code, OHOS::MessageParcel &data, OHOS::MessageParcel &reply, OHOS::MessageOption &option)
{
    if (GetDescriptor() != data.ReadInterfaceToken()) {
        LOG_ERROR("IPC descriptor is  not equal.");
        return INVALID_FD;
    }

    if (code >= static_cast<uint32_t>(ResultSetCode::FUNC_BUTT)) {
        LOG_ERROR("OnRemoteRequest method code(%{public}d) out of range.", code);
        return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
    }
    Handler handler = handlers[code];
    if (handler == nullptr) {
        LOG_ERROR("OnRemoteRequest method code(%{public}d) is not support.", code);
        return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
    }
    return (this->*handler)(data, reply);
}

int ISharedResultSetStub::HandleGetRowCountRequest(MessageParcel &data, MessageParcel &reply)
{
    int count = -1;
    int errCode = resultSet_->GetRowCount(count);
    reply.WriteInt32(errCode);
    if (errCode == E_OK) {
        reply.WriteInt32(count);
    }
    LOG_DEBUG("HandleGetRowCountRequest call %{public}d", errCode);
    return NO_ERROR;
}

int ISharedResultSetStub::HandleGetColumnNamesRequest(MessageParcel &data, MessageParcel &reply)
{
    auto [errCode, names] = GetColumnNames();
    reply.WriteInt32(errCode);
    if (errCode == E_OK) {
        reply.WriteStringVector(names);
    }
    LOG_DEBUG("HandleGetColumnNamesRequest call %{public}d", errCode);
    return NO_ERROR;
}

int ISharedResultSetStub::HandleOnGoRequest(MessageParcel &data, MessageParcel &reply)
{
    int oldRow = data.ReadInt32();
    int newRow = data.ReadInt32();
    auto errCode = OnGo(oldRow, newRow);
    reply.WriteInt32(errCode);
    LOG_DEBUG("HandleOnGoRequest call %{public}d", errCode);
    return NO_ERROR;
}

int ISharedResultSetStub::HandleCloseRequest(MessageParcel &data, MessageParcel &reply)
{
    int errCode = resultSet_->Close();
    reply.WriteInt32(errCode);
    LOG_DEBUG("HandleCloseRequest call %{public}d", errCode);
    return NO_ERROR;
}

std::pair<int, std::vector<std::string>> ISharedResultSetStub::GetColumnNames()
{
    std::vector<std::string> columnNames;
    auto ret = resultSet_->GetAllColumnNames(columnNames);
    return {ret, columnNames};
}

int32_t ISharedResultSetStub::OnGo(int32_t start, int32_t target)
{
    int rowCount = 0;
    resultSet_->GetRowCount(rowCount);
    if (start < 0 || target < 0 || target >= rowCount) {
        LOG_ERROR("Invalid targetRowIndex: %{public}d.", rowCount);
        return -1;
    }

    int columnCount = 0;
    resultSet_->GetColumnCount(columnCount);
    if (columnCount <= 0) {
        LOG_ERROR("Invalid columnCount: %{public}d.", columnCount);
        return -1;
    }
    LOG_DEBUG("rowCount: %{public}d, columnCount: %{public}d.", rowCount, columnCount);

    bool bResultSet = false;
    resultSet_->IsStarted(bResultSet);
    if (!bResultSet) {
        resultSet_->GoToFirstRow();
    }

    int errCode = resultSet_->GoToRow(start);
    if (errCode) {
        LOG_ERROR("Go to row %{public}d failed.", start);
        return -1;
    }

    return WriteBlock(start, target, columnCount);
}

int32_t ISharedResultSetStub::WriteBlock(int32_t start, int32_t target, int columnCount)
{
    int errCode = 0;
    int row = start;

    while (!errCode && row <= target) {
        int status = writer_->AllocRow();
        if (status != 0) {
            LOG_ERROR("SharedBlock is full.");
            return row - 1;
        }

        WriteColumn(columnCount, row);
        row++;
        errCode = resultSet_->GoToNextRow();
    }
    return target;
}

void ISharedResultSetStub::WriteColumn(int columnCount, int row)
{
    for (int i = 0; i < columnCount; i++) {
        ColumnType type;
        resultSet_->GetColumnType(i, type);
        switch (type) {
            case ColumnType::TYPE_INTEGER:
                int64_t value;
                resultSet_->GetLong(i, value);
                if (writer_->Write(i, value)) {
                    LOG_DEBUG("WriteLong failed of row: %{public}d, column: %{public}d", row, i);
                }
                break;
            case ColumnType::TYPE_FLOAT:
                double dValue;
                resultSet_->GetDouble(i, dValue);
                if (writer_->Write(i, dValue)) {
                    LOG_DEBUG("WriteDouble failed of row: %{public}d, column: %{public}d", row, i);
                }
                break;
            case ColumnType::TYPE_NULL:
                if (writer_->Write(i)) {
                    LOG_DEBUG("WriteNull failed of row: row: %{public}d, column: %{public}d", row, i);
                }
                break;
            case ColumnType::TYPE_BLOB:
                if (WriteBlobData(i)) {
                    LOG_DEBUG("WriteBlob failed of row: %{public}d, column: %{public}d", row, i);
                }
                break;
            default:
                std::string stringValue;
                resultSet_->GetString(i, stringValue);
                if (writer_->Write(i, stringValue.c_str(), stringValue.size() + 1)) {
                    LOG_DEBUG("WriteString failed of row: %{public}d, column: %{public}d", row, i);
                }
        }
    }
}

bool ISharedResultSetStub::WriteBlobData(int column)
{
    std::vector<uint8_t> blobValue;
    resultSet_->GetBlob(column, blobValue);
    if (blobValue.empty()) {
        return false;
    }

    return writer_->Write(column, &blobValue[0], blobValue.size() * sizeof(uint8_t));
}

std::shared_ptr<AppDataFwk::SharedBlock> ISharedResultSetStub::GetBlock()
{
    return writer_->GetBlock();
}
} // namespace OHOS::NativeRdb